package com.alibaba.incubator.loadtestermini.model.behavior.organism;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

import com.alibaba.incubator.loadtestermini.model.behavior.SharedResourceHolder;
import com.alibaba.incubator.loadtestermini.model.load.SessionDistribution;
import com.alibaba.incubator.loadtestermini.model.load.ThinkingTime;
import com.alibaba.incubator.loadtestermini.model.measurement.recorder.SceneRecorder;
import com.alibaba.incubator.loadtestermini.model.measurement.report.SceneMeasurement;

/**
 * MUST support reuse<br/>
 * (created at 2010-5-27)
 * 
 * @author <a href="mailto:shuo.qius@alibaba-inc.com">QIU Shuo</a>
 */
public abstract class Scene implements SharedResourceHolder, InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(Scene.class);

    public String getName() {
        return getClass().getName();
    }

    private SessionDistribution sessionDistribution;
    private int concurrency;
    private ThinkingTime thinkingTime;
    private long duration;

    public void setDuration(long duration) {
        this.duration = duration;
    }

    public void setThinkingTime(ThinkingTime thinkingTime) {
        this.thinkingTime = thinkingTime;
    }

    public void setConcurrency(int concurrency) {
        this.concurrency = concurrency;
    }

    public void setSessionDistribution(SessionDistribution sessionDistribution) {
        this.sessionDistribution = sessionDistribution;
    }

    private Long recordInterval;

    public void setRecordInterval(long recordInterval) {
        this.recordInterval = recordInterval;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (sessionDistribution == null)
            throw new IllegalArgumentException("property 'sessionDistribution' is null!");
        if (concurrency <= 0)
            throw new IllegalArgumentException("property 'concurrency' must bigger than 0!");
        if (thinkingTime == null)
            throw new IllegalArgumentException("property 'thinkingTime' is null!");
        if (duration <= 0)
            throw new IllegalArgumentException("property 'duration' must bigger than 0!");
        if (recordInterval == null)
            throw new IllegalArgumentException("property 'recordInterval' is null!");
    }

    //----------------------------------------------------------------------------------------
    private SceneRecorder sceneResultRecorder;
    private List<SceneMeasurement> measurements;
    private List<ExecutionThread> exeThrdList;

    private AtomicBoolean started = new AtomicBoolean(false);

    public void startup(String scenarioResultRootPath) {
        if (started.getAndSet(true))
            return;
        sceneResultRecorder = new SceneRecorder();
        exeThrdList = new ArrayList<ExecutionThread>();
        executionLatch = new CountDownLatch(concurrency);
        initSharedResource();
    }

    /**
     * @param scenarioResultRootDir absolute path
     */
    public void execute() throws Exception {
        for (int i = 0; i < concurrency; ++i) {
            ExecutionThread executionThread = new ExecutionThread();
            exeThrdList.add(executionThread);
        }
        for (ExecutionThread executionThread : exeThrdList) {
            executionThread.start();
        }
        executionLatch.await();

        logger.info("-------->scene time begin");
        final long startTime = System.currentTimeMillis();
        sceneResultRecorder.startRecord();

        try {
            while (System.currentTimeMillis() < startTime + duration) {
                Thread.sleep(recordInterval);
                sceneResultRecorder.snapshot();
            }
            logger.info("<--------scene time end");
        } catch (Exception e) {
            logger.warn("<--------scene time exception!");
            throw e;
        } finally {
            measurements = sceneResultRecorder.getRecords();
        }
    }

    protected void dealWithResult() {
        //QS_TODO
    }

    public void shutdown() {
        if (!started.getAndSet(false))
            return;
        for (ExecutionThread executionThread : exeThrdList)
            executionThread.shutdown();
        clearSharedResource();

        dealWithResult();

        sceneResultRecorder = null;
        exeThrdList = null;
        executionLatch = null;
        measurements = null;
    }

    //----------------------------------------------------------------------------------------
    private CountDownLatch executionLatch;

    private class ExecutionThread extends Thread {
        private volatile boolean keepRunning = true;
        private CountDownLatch shutdownLatch = new CountDownLatch(1);

        /**
         * called by thread of
         * {@link com.alibaba.incubator.loadtestermini.model.behavior.organism.Scenario
         * Scenario}
         */
        public void shutdown() {
            keepRunning = false;
            interrupt();
            try {
                shutdownLatch.await();
            } catch (InterruptedException e) {
            }
        }

        @Override
        public void run() {
            executionLatch.countDown();
            while (keepRunning) {
                Session session = Scene.this.sessionDistribution.selectSession();
                try {
                    session.execute(Scene.this.thinkingTime, Scene.this, Scene.this.sceneResultRecorder);
                } catch (InterruptedException ie) {
                } catch (Exception e) {
                    Scene.this.sceneResultRecorder.setErrorOccur();
                    Scene.logger.error(session.toString(), e);
                    break;
                }
            }
            shutdownLatch.countDown();
        }
    }
}
